package com.sunday.common.mq.rabbit.study.base.e7_RPC;

import com.rabbitmq.client.*;
import com.sunday.common.mq.rabbit.study.base.ConnectionUtil;
import lombok.extern.slf4j.Slf4j;

/**
 * 远程过程调用 （RPC）
 * <p>
 * 在第二个教程中，我们学习了如何 使用工作队列在多个任务之间分配耗时的任务 工人。
 * 但是，如果我们需要在远程计算机上运行函数并等待 结果呢？嗯，那是另一回事了。这种模式很常见 称为远程过程调用或 RPC。
 * 在本教程中，我们将使用 RabbitMQ 来构建一个 RPC 系统： 客户端和可扩展的 RPC 服务器。因为我们没有任何耗时 值得分发的任务，我们将创建一个虚拟的 RPC 返回斐波那契数的服务。
 * <p>
 * 一般来说，通过RabbitMQ进行RPC很容易。客户端发送请求消息，服务器回复响应消息。
 * 为了接收响应，我们需要发送带有请求的“回调”队列地址。我们可以使用默认队列（在Java客户端中是独占的）。我们来试试吧：
 * <p>
 * <p>
 * 关于RPC的说明
 * <p>
 * 尽管RPC在计算中是一种非常常见的模式，但它经常受到批评。当程序员不知道函数调用是本地的还是慢的RPC时，会出现问题。这样的混淆导致系统不可预测，
 * 并增加了调试的不必要的复杂性。错误使用RPC可以导致不可维护的意大利面条代码，而不是简化软件。
 * <p>
 * 考虑到这一点，请考虑以下建议：
 * <p>
 * 确保明显哪个函数调用是本地的，哪个是远程的。 记录您的系统。使组件之间的依赖关系变得清晰。 处理错误案例。当RPC服务器长时间停机时，客户端应该如何反应？
 * 如有疑问，请避免使用RPC。如果可以，您应该使用异步管道 - 而不是类似RPC的阻塞，将结果异步推送到下一个计算阶段。
 * <p>
 * 远程过程调用协议 编辑 同义词 RPC一般指远程过程调用协议 RPC（Remote ProcedureCall）—远程过程调用
 * <p>
 * 它是一种通过网络从远程计算机程序上请求服务，而不需要了解底层网络技术的协议。RPC协议假定某些传输协议的存在，如TCP或UDP，
 * 为通信程序之间携带信息数据。在OSI网络通信模型中，RPC跨越了传输层和应用层。RPC使得开发包括网络分布式多程序在内的应用程序更加容易。
 * RPC采用客户机/服务器模式。请求程序就是一个客户机，而服务提供程序就是一个服务器。首先，客户机调用进程发送一个有进程参数的调用信息到服务进程，
 * 然后等待应答信息。在服务器端，进程保持睡眠状态直到调用信息到达为止。当一个调用信息到达，服务器获得进程参数，计算结果，发送答复信息，然后等待下一个调用信息，
 * 最后，客户端调用进程接收答复信息，获得进程结果，然后调用执行继续进行。 有多种 RPC模式和执行。最初由 Sun 公司提出。IETF ONC
 * 宪章重新修订了 Sun 版本，使得 ONC RPC 协议成为 IETF 标准协议。现在使用最普遍的模式和执行是开放式软件基础的分布式计算环境（DCE）。
 */
@Slf4j
public class RPCServer {

    private static final String RPC_QUEUE_NAME = "rpc_queue";

    private static int fib(int n) {
        if (n == 0) return 0;
        if (n == 1) return 1;
        return fib(n - 1) + fib(n - 2);
    }

    public static void main(String[] argv) throws Exception {

        ConnectionFactory factory = ConnectionUtil.factory();

        Connection connection = factory.newConnection();
        Channel channel = connection.createChannel();
        channel.queueDeclare(RPC_QUEUE_NAME, false, false, false, null);
        channel.queuePurge(RPC_QUEUE_NAME);

        channel.basicQos(1);

        log.info(" [x] Awaiting RPC requests");

        // RPC Server 获取结果
        DeliverCallback deliverCallback = (consumerTag, delivery) -> {
            AMQP.BasicProperties replyProps = new AMQP.BasicProperties
                    .Builder()
                    .correlationId(delivery.getProperties().getCorrelationId())
                    .build();

            String response = "";
            try {
                String message = new String(delivery.getBody(), "UTF-8");
                int n = Integer.parseInt(message);

                log.info(" [.] fib({})", message);
                response += fib(n);
            } catch (RuntimeException e) {
                log.info(" [.] ", e);
            } finally {
                // 将结果响应给 RPC Client, 并且通过 client端发送的 properties.replyTo(replyQueueName) 值进行报告推送
                channel.basicPublish("", delivery.getProperties().getReplyTo(), replyProps, response.getBytes("UTF-8"));
                // 与rabbitmq交互ack确认
                channel.basicAck(delivery.getEnvelope().getDeliveryTag(), false);
            }
        };

        channel.basicConsume(RPC_QUEUE_NAME, false, deliverCallback, (consumerTag -> {
        }));
    }
}